home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Gold Collection / Software Vault - The Gold Collection (American Databankers) (1993).ISO / cdr49 / 262_01.zip / CMENU.TXT < prev    next >
Text File  |  1993-04-14  |  12KB  |  300 lines

  1. .ce 2
  2. Help for Those Really Long Dumb Menu Programs
  3. by Robert Ramey
  4.  
  5. I just got my Hewlett Packard Laser Jet II.  It seems to have
  6. everything.  All I have to do is read the manual so I can
  7. set up the correct printer control codes.  This machine has a zillon
  8. options, all with funny codes and rules about which combinations
  9. of codes and options are valid.
  10. If I want to avoid a lot of tedious work sifting through manual
  11. everytime I want to change the printer setup,  I'll have to
  12. write a program.  Since I'm a programmer I don't mind
  13. This is an application made to order for a series of menus.
  14. Now if you've written one menu driven program you've written
  15. them all.  Or at least it seems that way.  This is another
  16. tedious job.  Its still better than sifting through the printer
  17. manual all the time as its only done once.  However,  writing
  18. a menu driven program is tedious in itself.
  19.  
  20. I decided that this was the last tedious menu program I was going
  21. to write.
  22.  
  23. 1. Table Driven Code.
  24.  
  25. My solution is to write once a small piece of code which
  26. handles dialog with the operator using a table of menus as a
  27. guide.  Then for each new menu application all I have to
  28. write are the menus themselves and some "action routines"
  29. which are invoked when certain points in the menu dialog are
  30. touched.
  31.  
  32. Listing 1 shows the menus used for my laser printer program.
  33. The labels place each menu in its proper place in the hierarchy.
  34. At any time the operator can respond with one of the menu
  35. selections, return, or escape.
  36. Generally, escape will end the menu sequence. Return will return
  37. to the previous menu, and a valid menu selection will display
  38. a sub menu.
  39. For example, the top menu is the first to be displayed.
  40. If the operator selects 2 the menu labeled 2 is displayed.
  41. If the operator then selects 1 the menu labeled 21 is displayed.
  42.  
  43. This flow of control will end if a menu selection has no sub
  44. menu or a specific "action routine" is specified.
  45. In this example,  For menu
  46. selection "4. Just exit" I have chosen exit(0) as the action
  47. routine.  When this selection is chosen function exit() will
  48. be called with argument 0.
  49. If there is no sub menu nor is there any specific "action routine"
  50. a function action() will be called with a character pointer argument.
  51. This argument contains the sequence of responses that led to the
  52. current position in the hierarchy.
  53.  
  54. The basic idea of this program is that the operator moves around
  55. the hierarchy of menus at will.
  56. Listing 2 shows an example of the laser printer program in action.
  57.  
  58. Listing 3 shows how my laser printer setup program functions with
  59. the menus.  The main program calls the library function menu()
  60. then exits.  The program doesn't specify the flow of control.
  61. This was specified in the menus.
  62. Later I'll show the code for menu() along with
  63. how the menus get into the program itself.
  64. Now the only thing left is to code the action routines.
  65. As the operator makes selections from the menus below selection
  66. "2. Alter print setup", function action() is executed.  This records
  67. his choices in the array p.
  68. From time to time he may select
  69. option "1. Display current printer setup" to see which combination
  70. he has selected so far and to check that is valid.
  71. This will invoke the function display().
  72. When he is satisfied, he selects "3. Write printer control codes
  73. and exit".  The finish() function is then executed which writes
  74. the proper codes to the output file.
  75.  
  76. Separating the menus and flow of control from the
  77. action routines makes writing a program
  78. much more like writing a suite of small almost independent
  79. programs.  It also makes it much easier to expand the program:
  80. All one must do is add on to menus and the action routines.
  81. The rest of the program need not be touched.
  82.  
  83. 2. How It Works
  84.  
  85. Each menu is stored in a data structure like that shown in listing 4.
  86. To initiate a menu dialog the main program calls menu() with the
  87. address of the initial menu as the parameter.
  88.  
  89. Listing 5 shows the menu() function.  menu() displays the menu
  90. whose address it has recieved as an argument a : character for
  91. a prompt and waits for a response.  When a response is recieved
  92. it is compared against the first character in each line of the
  93. menu.  If it matches, the corresponding action routine is called
  94. with its associated parameter.  A hierarchy of menus is implemented
  95. by specifying menu() as the action routine with the address of the
  96. sub menu as the parameter.
  97.  
  98. When an action routine (including menu()) returns,  it should specify
  99. which of the previously displayed menus should be repeated.
  100. If it returns a value of 0, the immediatly previous menu will be
  101. repeated.  If it returns a value of 1, the menu two levels back
  102. will be repeated, etc.
  103. In our laser printer setup program example, the function copies()
  104. returns a value of 0 so that the "What do you want to change" menu
  105. will be repeated.  The function action() returns 1 so that
  106. menus such as
  107.  
  108.     Choose One.
  109.     1. Portrait(vertical)
  110.     2. Landscape(horizontal)
  111.  
  112. will not be repeated immediatly after a choice is made.
  113. When ever the operator responds to a menu with RETURN, menu()
  114. returns a value of 0 which repeats the previous menu.
  115. Whenever the operator responds with ESCAPE, menu()
  116. returns a value of 99 which will normally return through all the menus
  117. back to the initial menu() call.
  118.  
  119. Listing 6 show the C code which implements the menus originally
  120. displayed in Listing 1.  This should help to clarify the functioning
  121. of the menu() function as well as the the laser printer setup
  122. program.  To summarize,  A table driven menu program consists of
  123. three separatly compiled modules:
  124.  
  125. .in +4
  126. A set of action routines along with a main program which calls menu(&m)
  127.  
  128. A set of menu data structures containing the text of the menus,
  129. action routine addresses and paramters
  130.  
  131. A library function menu()
  132. .in -4
  133.  
  134. On my system, the laser printer setup program is produced by the
  135. following commands:
  136.  
  137.     cc slptrm
  138.     cc slptr
  139.     zlink slptr,slptrm,crunlib/
  140.  
  141. 3. Polishing Up the System.
  142.  
  143. So far we have simplfied the preparation of menu driven programs
  144. by separating the menus from the program code.
  145. Now we are now left with
  146. the task of coding the original menu into a C data structure.
  147. This is almost as bad as coding the menus into the original program.
  148. However, the job of translating the menus in listing 1 to the
  149. C program module in listing 6 is completely mechanical.  It can
  150. be turned over to a program we might call a menu compiler.
  151.  
  152. Listing 7 shows the program cmenu.c.  This programs reads a file
  153. in the format of list 1 and writes a file in the format of listing 6.
  154. If the menu in figure 1 is in a file named slptrm.mnu, the new
  155. command sequence is:
  156.  
  157.     cmenu <a:slptrm.mnu >slptrm.c
  158.     cc slptrm
  159.     cc slptr
  160.     zlink slptr,slptrm,crunlib/
  161.  
  162. Each menu in the input file to menu compiler should be in the
  163. following format:
  164.  
  165. .in +4
  166. The menu index indicating its position in the hierarchy
  167. should start in column 1.
  168.  
  169. Subsequent lines should start with tab.
  170.  
  171. The menu question on one or more lines.
  172.  
  173. The menu selections one per line.  Menu selections are
  174. distinguished by a '.' in the second character
  175. position of the text.
  176. .in -4
  177.  
  178. Each menu selection may be followed by a ';' and an action routine
  179. and parameter.  If no action routine is specified, the menu
  180. compiler will fill in a default action routine and parameter.
  181. If the menu indices indicate that a sub menu exists, the
  182. default action routine is menu() and the default parameter is
  183. the address of the submenu.  If there exists no sub menu,
  184. the default action routine is action() and the default parameter
  185. is a pointer to a string which contains the menu responses which
  186. brought us to this point.
  187.  
  188. Sometimes I want to insert an action routine within the
  189. hierarchy of menus.  For example, consider the following
  190. menu and code fragment from a modem transfer program:
  191.  
  192. .nf
  193.     ...
  194.     4. send file;gfname(m4)
  195.     ...
  196. 4
  197.     Which prototcal do you want to use?
  198.     1. XMODEM with check sum
  199.     2. XMODEM with crc
  200.     3. YMODEM
  201.     4. Kermit
  202.     5. Simple Xon-Xoff
  203.     6. No protocal at all
  204.   
  205.  
  206. FILE *df;   /* disk file pointer */
  207. char filename[MAXFNLENGTH]; /* file name specified by operator */
  208. int gfname(nextmenu)
  209. MENU *nextmenu;
  210. {
  211.     fprintf(stderr,"\ntype in file name:");
  212.     fgets(filename, MAXFNLENGTH, stdin);
  213.     if(df = fopen(filename, "r")){
  214.         menu(nextmenu); /* continue on with menus */
  215.         fclose(df); /* close file on returning from submenu */
  216.     }
  217.     else
  218.         /* simply display error message */
  219.         fprintf(stderr,"\nfile not found");
  220.     return 0;
  221. }
  222. .fi
  223.  
  224. When the operator selects "4. send file" the function gfname is
  225. called and a filename is requested.  If the fule name is valid
  226. the menu dialog continues on to the sub menu 4.  Otherwise, an
  227. error message is displayed and control returns to the previous
  228. menu.
  229.  
  230. This permits me to keep a potentially complicated program in small
  231. almost independent fragments.  I have to be careful of side effects
  232. however.  In general, one should undo any side effects on leaving
  233. that were set in the action routine.  In this case this boils
  234. down to closing the opened disk file before returning from gfname().
  235.  
  236. One final trick which might come in handy is to modify at runtime
  237. the action routine addresses or parameters in the menu data
  238. structure.  This would mean that some menu selections would
  239. could alter the position of menus within the hierarchy.  Although
  240. I havn't done it yet It is interesting to think about.  It could
  241. allow a program to adapt itself to more experienced users.  It
  242. smells like self modifying code which is supposed to be no-no.
  243. However,  I want to experment more in this area before I discard
  244. the alternative.
  245.  
  246. 4. Extending the Idea.
  247.  
  248. There a number of ways the basic idea could be altered that might
  249. be interesting.  First, menu tables could be loaded at run time.
  250. instead of compiled into the program. This would permit translation
  251. of programs into foreign languages without recompiling or relinking.
  252. I decided to compile in the menus for the following reasons:
  253.  
  254. .in +4
  255. it usually saves some disk space since file sizes grow in
  256. increments of 2k on my system.
  257.  
  258. having menu data and code in the same file simplifies copying
  259. of programs and reduces the number of files.
  260.  
  261. it minimizes size of program as code to read, interpret and
  262. load menu data structures does not have to be included.
  263.  
  264. it easier to incorporate action routine addresses and
  265. parameters at compile time.
  266. .in -4
  267.  
  268. Another idea would be to extend the menu() function to include
  269. help displays any time a help key was pressed.  The menu
  270. compiler would be extended accordingly.
  271.  
  272. Finally, the menu() function could adjusted for memory mapped
  273. systems to permit pop-up menus.
  274. None of these changes would alter the menu file
  275. nor the action routines of programs currently functioning.
  276.  
  277. 5. A Fly in the Ointment.
  278.  
  279. When I finally got cmenu.c functioning I ran into a suprise.
  280. My C compiler, Q/C, will not initialize structures.  Hence,
  281. the code produced by cmenu.c was not compilable.  However, I was
  282. determined to eliminate the tedious work of coding menu
  283. programs, even though it involved more tedious coding to do so.
  284. I wrote a second version of cmenu.c which produces a program
  285. module written in Z-80 assembler instead of C.  This is listing 7.
  286. Its faster and more convenient than the C version,  but is not
  287. so portable.  It can be modified to function with most other
  288. assemblers.
  289. Now the command sequence to compile and link the laser printer
  290. setup program is:
  291.  
  292.     cmenu <a:slptrm.mnu >slptrm.z80
  293.     zas slptrm
  294.     cc slptr
  295.     zlink slptr,slptrm,crunlib/
  296.  
  297. Now I'm all set to write that next menu driven program.
  298. Of course, I can't think of any I need right now.
  299. It figures.
  300.